package j_UnionFind;

// 我们的第五版Union-Find (Path Compression)
/**
 * 如果内存空间是一个问题，可以试着把rank去掉，只留下路径压缩，效率当然会降低，但是一般可以接受；
 */
public class UnionFind5_pathcp_FINAL_VERSION implements UF {

    // rank[i]表示以i为根的集合所表示的树的层数
    // 在后续的代码中, 我们并不会维护rank的语意, 也就是rank的值在路径压缩的过程中, 有可能不在是树的层数值
    // 这也是我们的rank不叫height或者depth的原因, 他只是作为比较的一个标准
    private int[] rank;
    private int[] parent; // parent[i]表示第i个元素所指向的父节点

    // 构造函数
    public UnionFind5_pathcp_FINAL_VERSION(int size){

        rank = new int[size];
        parent = new int[size];

        // 初始化, 每一个parent[i]指向自己, 表示每一个元素自己自成一个集合
        for( int i = 0 ; i < size ; i ++ ){
            parent[i] = i;
            rank[i] = 1;
        }
    }

    @Override
    public int getSize(){
        return parent.length;
    }

    // 查找过程, 查找元素p所对应的集合编号
    // O(h)复杂度, h为树的高度
    private int find(int p){
        if(p < 0 || p >= parent.length)
            throw new IllegalArgumentException("p is out of bound.");

        while( p != parent[p] ){             	
        	parent[p] = parent[parent[p]];//*压缩*；让 父节点的指针 指向 爷爷节点；为什么路径压缩的时候不用维护rank值：因为rank的作用只有一个：比较在union时，两个root间的深度关系；                 
            p = parent[p];  //如果r1>r2，那就r2->r1，如果r1=r2，就还让r2->r1，而只有r1<r2时，需要让r1->r2;也就是说只要路径压缩 不出现rank大而depth小的情况就行，即rank大小能和depth大小对应上就行，而且不用管相等的情况；
        }				//分析：假设r1>r2,如果压缩后仍然r1>r2，则不维护rank也可以代表深度关系；如果压缩后r1=r2，此时怎样都可以，所以r2->r1仍然没问题；最坏的情况就是:r1的深度只比r2多1，且r1压缩，r2不压缩；
        			//合并的原则只有：Lv1+Lv1->Lv2，LvN+(LvN-)->LvN; 画画图可以发现，正常同Lv合成的高深度树，其深度总是由一个凸出来的叶子决定的；而一个高Lv树和一个低Lv树合成时，总能看作是高Lv树里包含的那俩和低Lv同深度的树，多了一个凸出来的分支而已；
        return p;  //由此可以发现，一次find操作进行的一个节点的路径压缩，想压缩只能压缩凸出来的叶子，而压缩该节点后其上一层最起码总还有上一Lv的树凸出来的叶子，所以不管路径压缩时向上移动了几次，总体的depth*最多-1*，所以说最多是变成待合成的两棵树depth一样的情况，即不会出现原本rank大的树压缩后rank小于对方的情况，所以不需要维护rank！！！！
    }

    // 查看元素p和元素q是否所属一个集合
    // O(h)复杂度, h为树的高度
    @Override
    public boolean isConnected( int p , int q ){
        return find(p) == find(q);
    }

    // 合并元素p和元素q所属的集合
    // O(h)复杂度, h为树的高度
    @Override
    public void unionElements(int p, int q){

        int pRoot = find(p);
        int qRoot = find(q);

        if( pRoot == qRoot ) //如果所有节点全部连通了，再调用就会直接return；节点在同一个集合内
            return;

        // 根据两个元素所在树的rank不同判断合并方向
        // 将rank低的集合合并到rank高的集合上
        if( rank[pRoot] < rank[qRoot] )
            parent[pRoot] = qRoot;
        else if( rank[qRoot] < rank[pRoot])
            parent[qRoot] = pRoot;
        else{ // rank[pRoot] == rank[qRoot]
            parent[pRoot] = qRoot;
            rank[qRoot] += 1;   // 此时, 我维护rank的值
        }
    }
}